package bancosys.tec.persist.span.fat;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.persist.span.SpanAwareBean;
import bancosys.tec.persist.span.SpanAwareBeanComparator;
import bancosys.tec.persist.validator.AbstractBeanValidator;
import bancosys.tec.persist.validator.ValidationError;
import bancosys.tec.utils.date.Date;

/**
 * @created Mar 2, 2007
 * @author Rafael Volpato
 * @deprecated there is currently no alternative, spaning entities are being reviewed.
 */
@Deprecated
public class FatSpanAwareValidator extends AbstractBeanValidator {

    private static final Logger LOG = Logger.getLogger(FatSpanAwareValidator.class);

    /**
     * Validações de inserção.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> validateInsert(Persistable bean) {
        List<ValidationError> errors = super.validateInsert(bean);
        if (((FatSpanAwareBean<?>) bean).getVigencias().size() != 1) {
            errors = this.addError(errors, new ValidationError("persist.validationError.objectMustHaveOneTermOnly", bean));
        }

        return errors;
    }

    /**
     * Validações de autorizacao de inserção.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> validateInsertAuthorization(Persistable bean) {
        List<ValidationError> superErrors = super.validateInsertAuthorization(bean);
        List<ValidationError> validationErrors = this.validateInsert(bean);
        return this.sumErrors(superErrors, validationErrors);
    }

    /**
     * Validações de atualização.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> validateUpdate(Persistable bean) {
        List<ValidationError> errors = super.validateUpdate(bean);
        errors = this.addError(errors, this.validarConsistenciaDatas(bean));
        errors = this.addError(errors, this.validarVigencias(bean));
        return errors;
    }

    /**
     * Validações de autorizacao de atualização.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> validateUpdateAuthorization(Persistable bean) {
        List<ValidationError> superErrors = super.validateUpdateAuthorization(bean);
        List<ValidationError> validationErrors = this.validateUpdate(bean);
        return this.sumErrors(superErrors, validationErrors);
    }

    /**
     * Validações para remover bean.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> validateRemove(Persistable bean) {
        List<ValidationError> errors = super.validateRemove(bean);
        errors = this.addError(errors, this.validarConsistenciaDatas(bean));
        errors = this.addError(errors, this.validarVigencias(bean));
        return errors;
    }

    /**
     * Validações de autorizacao para remover bean.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> validateRemoveAuthorization(Persistable bean) {
        List<ValidationError> superErrors = super.validateRemoveAuthorization(bean);
        List<ValidationError> validationErrors = this.validateRemove(bean);
        return this.sumErrors(superErrors, validationErrors);
    }

    /**
     * Validações de inserção.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> customInsertValidation(Persistable bean) {
        return null;
    }

    /**
     * Validações de remoção.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> customRemoveValidation(Persistable bean) {
        return null;
    }

    /**
     * Validações de atualização.
     * 
     * @param bean bean.
     * @return lista de erros ou <code>null</code> se não houver erros.
     */
    @Override
    public List<ValidationError> customUpdateValidation(Persistable bean) {
        return null;
    }

    /**
     * Verifica se as datas das vigências estão consistentes.
     * 
     * @param bean o bean a ser verificado.
     * @return {@link ValidationError} caso não seja válida a insercao/remocao, <code>null</code> caso contrário.
     */
    @SuppressWarnings("unchecked")
    public ValidationError validarConsistenciaDatas(Persistable bean) {
        List<? extends SpanAwareBean> vigencias = ((FatSpanAwareBean) bean).getVigencias();

        // Verificar se o bean possui pelo menos uma vigência
        if (vigencias.size() == 0) {
            return new ValidationError("persist.validationError.atLeastOneTerm", bean);
        }

        Collections.sort(vigencias, new SpanAwareBeanComparator());

        // Verifica se todas as vigências anteriores à última estão com as datas de início e término coerentes
        Date dataFimAnterior = null;
        for (int i = 0; i < vigencias.size() - 1; i++) {
            SpanAwareBean vigencia = vigencias.get(i);
            if (vigencia.getDataInicio() == null) {
                return new ValidationError("persist.validationError.termStartDateMissing", bean);
            }

            if (vigencia.getDataFim() == null) {
                return new ValidationError("persist.validationError.termEndDateMissing", bean);
            }

            if (vigencia.getDataInicio().after(vigencia.getDataFim())) {
                return new ValidationError("persist.validationError.termStartAfterEnd", bean, vigencia.getDataInicio(),
                        vigencia.getDataFim());
            }

            // para a vigencia mais antiga (1a posicao)
            if (dataFimAnterior != null) {
                Date dataInicio = ((Date) dataFimAnterior.clone()).addDays(1);
                if (!vigencia.getDataInicio().equals(dataInicio)) {
                    // A data de início (vigencia.getDataInicio()) deve ser igual à data da vigência anterior mais um dia.
                    return new ValidationError("persist.validationError.termStartMustMatchPriorTerm", bean, vigencia.getDataInicio());
                }
            }

            dataFimAnterior = vigencia.getDataFim();
        }

        // Para a última vigência o tratamento é diferente.
        // A data de início pode ser anterior à data de término da vigência anterior, mas não pode ser anterior
        // à data de início da vigência anterior.
        SpanAwareBean ultimaVigencia = vigencias.get(vigencias.size() - 1);
        if (ultimaVigencia.getDataFim() != null && ultimaVigencia.getDataInicio().after(ultimaVigencia.getDataFim())) {
            return new ValidationError("persist.validationError.termStartAfterEnd", bean, ultimaVigencia.getDataInicio(),
                    ultimaVigencia.getDataFim());
        }
        if (vigencias.size() >= 2) {
            SpanAwareBean penultimaVigencia = vigencias.get(vigencias.size() - 2);
            if (!ultimaVigencia.getDataInicio().after(penultimaVigencia.getDataInicio())) {
                return new ValidationError("persist.validationError.lastTermStartBeforePriorTermStart", bean,
                        ultimaVigencia.getDataInicio(), penultimaVigencia.getDataInicio());
            }
        }

        return null;
    }

    /**
     * Verifica se a tentativa de inserir/remover uma nova vigencia é válida.
     * 
     * @param originalBean o bean a ser verificado.
     * @return {@link ValidationError} caso não seja válida a insercao/remocao, <code>null</code> caso contrário.
     */
    @SuppressWarnings("unchecked")
    public ValidationError validarVigencias(Persistable originalBean) {
        FatSpanAwareBean bean = (FatSpanAwareBean) originalBean;

        this.getPersistenceController().startTransaction();

        try {
            FatSpanAwareDAO dao = (FatSpanAwareDAO) bean.getController().getDAOFactory().getGenericDAO(bean.getClass());
            FatSpanAwareBean hotBean;
            try {
                hotBean = (FatSpanAwareBean) dao.findByPk(bean.getPk());
            } catch (BeanNotFoundException e) {
                return new ValidationError("persist.validationError.beanNotFoundOnHotPartition", bean);
            }

            List<SpanAwareBean> vigencias = new ArrayList<SpanAwareBean>(bean.getVigencias());
            List<SpanAwareBean> hotVigencias = new ArrayList<SpanAwareBean>(hotBean.getVigencias());

            final SpanAwareBeanComparator comparator = new SpanAwareBeanComparator();
            Collections.sort(vigencias, comparator);
            Collections.sort(hotVigencias, comparator);

            if (vigencias.size() > hotVigencias.size()) { /* Pelo menos uma vigência foi inserida */
                if (vigencias.size() - hotVigencias.size() > 1) {
                    /* Apenas uma vigência pode ser inserida */
                    return new ValidationError("persist.validationError.onlyOneTerm", bean);
                }
                if (!this.equals(vigencias, hotVigencias, hotVigencias.size())) {
                    return new ValidationError("persist.validationError.noTermUpdateWhileInserting", bean);
                }
            } else if (vigencias.size() < hotVigencias.size()) { /* Pelo menos uma vigência foi removida */
                if (hotVigencias.size() - vigencias.size() > 1) {
                    /* Apenas uma vigência pode ser removida */
                    return new ValidationError("persist.validationError.onlyOneRemovalAtATime", bean);
                }
                if (!this.equals(vigencias, hotVigencias, vigencias.size())) {
                    return new ValidationError("persist.validationError.noTermUpdateWhileRemoving", bean);
                }
            } else {
                /* As duas listas possuem o mesmo tamanho, ocorreu uma alteração de uma vigência ou nenhuma alteração nas vigências */
                if (!this.equals(vigencias, hotVigencias, vigencias.size() - 1)) {
                    return new ValidationError("persist.validationError.onlyLastTermCanBeInserted", bean);
                }
            }
            return null;
        } finally {
            // FIXME trocar para rollback?
            this.getPersistenceController().commit();
        }
    }

    /**
     * Valida se nenhum dos n primeiros elementos das listas possuí diferenças.
     * 
     * @param l1 a primeira lista.
     * @param l2 a segunda lista.
     * @param nElements o número de elementos que deve ser validado/
     * @return o erro encontrado
     */
    protected boolean equals(List<SpanAwareBean> l1, List<SpanAwareBean> l2, int nElements) {

        for (int i = 0; i < nElements; i++) {
            SpanAwareBean s1 = l1.get(i);
            SpanAwareBean s2 = l2.get(i);

            if (!this.equals(s1, s2)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Verifica se dois beans sao iguais.
     * 
     * @param bean1 primeiro bean.
     * @param bean2 segundo bean.
     * @return <code>true</code> caso os beans sejam iguais, <code>false</code> caso contrário.
     */
    protected boolean equals(SpanAwareBean bean1, SpanAwareBean bean2) {
        if (bean1 == null && bean2 == null) {
            return true;
        }

        if (bean1 == null || bean2 == null) {
            return false;
        }

        if (!bean1.getClass().equals(bean2.getClass())) {
            return false;
        }

        Collection<String> ignoreProperties = this.getIgnoreProperties();
        PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(bean1);
        for (PropertyDescriptor property : properties) {
            String propertyName = property.getName();
            if (ignoreProperties.contains(propertyName)) {
                continue;
            }
            try {
                Object o1 = BeanUtils.getProperty(bean1, propertyName);
                Object o2 = BeanUtils.getProperty(bean2, propertyName);
                if ((o1 != null) && (o2 != null) && (!o1.equals(o2))) {
                    LOG.debug("propriedade '" + propertyName + "' alterada na vigencia " + bean1 + ". Valores: + " + o1 + " e " + o2);
                    return false;
                }
            } catch (IllegalAccessException e) {
                // ignora
            } catch (InvocationTargetException e) {
                // ignora
            } catch (NoSuchMethodException e) {
                // ignora
            }
        }
        return true;
    }

    /**
     * Retorna as propriedades a serem ignoradas na verificacao dos beans de vigencias.
     * 
     * @return as propriedades a serem ignoradas na verificacao dos beans de vigencias.
     */
    protected Collection<String> getIgnoreProperties() {
        Collection<String> list = new LinkedList<String>();
        list.add("controller");
        list.add("persister");
        list.add("authorizer");
        list.add("purePersister");
        list.add("timeStamp");
        list.add("dirty");
        list.add("auth");
        return list;
    }
}
